﻿using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.EntityFrameworkCore;
using PMS.Core.Caching;
using PMS.Core.Configuration;
using PMS.Core.Infrastructure;
using PMS.Core.Infrastructure.DependencyManagement;
using PMS.Core.Redis;
using PMS.Data.DbUtils;
using PMS.Data.Entities.Stores;
using PMS.Services.Authentication;
using PMS.Services.Authentication.Externel;
using PMS.Services.Caching;
using PMS.Services.EntityServices.Common;
using PMS.Services.EntityServices.Customers;
using PMS.Services.EntityServices.Localization;
using PMS.Services.EntityServices.Logging;
using PMS.Services.EntityServices.Medias;
using PMS.Services.EntityServices.Messages;
using PMS.Services.EntityServices.NaviMenus;
using PMS.Services.EntityServices.SchduleTasks;
using PMS.Services.EntityServices.Security;
using PMS.Services.EntityServices.Settings;
using PMS.Services.EntityServices.Stores;
using PMS.Services.EntityServices.SystemModules;
using PMS.Services.Plugins;
using PMS.Services.SharedKernel;
using PMS.Services.Utils;
using PMS.Services.Utils.Captcha;
using PMS.Services.Utils.Messages;
using PMS.Services.Utils.ScheduleTasks;
using PMS.Services.Utils.Security;
using PMS.Web.Framework.Mvc.Routing;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace PMS.Web.Framework.Dependency
{
    /// <summary>
    /// Dependency registrar
    /// Autofac Core Services Register
    /// </summary>
    public class CoreDependencyRegistrar : IDependencyRegistrar
    {
        /// <summary>
        /// Register services and interfaces
        /// </summary>
        /// <param name="builder">Container builder</param>
        /// <param name="typeFinder">Type finder</param>
        /// <param name="config">Config</param>
        public virtual void Register(ContainerBuilder builder, ITypeFinder typeFinder, PMSConfig config)
        {
            //file provider
            builder.RegisterType<PMSFileProvider>().As<IPMSFileProvider>().InstancePerLifetimeScope();

            //web helper
            builder.RegisterType<WebHelper>().As<IWebHelper>().InstancePerLifetimeScope();

            //data layer
            builder.Register(context => new AppDbContext(context.Resolve<DbContextOptions<AppDbContext>>()))
                .As<IDbContext>().InstancePerLifetimeScope();
            //repositories
            builder.RegisterGeneric(typeof(EntityRepository<>)).As(typeof(IRepository<>)).InstancePerLifetimeScope();

            //plugins
            builder.RegisterType<PluginService>().As<IPluginService>().InstancePerLifetimeScope();

            //redis connection wrapper
            if (config.RedisEnabled)
            {
                builder.RegisterType<RedisConnectionWrapper>()
                    .As<ILocker>()
                    .As<IRedisConnectionWrapper>()
                    .SingleInstance();
            }

            //static cache manager
            if (config.RedisEnabled && config.UseRedisForCaching)
            {
                builder.RegisterType<RedisCacheManager>().As<IStaticCacheManager>().InstancePerLifetimeScope();
            }
            else
            {
                //Ensure Register EasyCaching Provider First
                builder.RegisterType<MemoryCacheManager>()
                    .As<ILocker>()
                    .As<IStaticCacheManager>()
                    .SingleInstance();
            }

            //work context
            builder.RegisterType<WebWorkContext>().As<IWorkContext>().InstancePerLifetimeScope();

            //store context
            builder.RegisterType<WebStoreContext>().As<IStoreContext>().InstancePerLifetimeScope();

            //services
            builder.RegisterType<EncryptionService>().As<IEncryptionService>().InstancePerLifetimeScope();
            builder.RegisterType<ActionContextAccessor>().As<IActionContextAccessor>().InstancePerLifetimeScope();
            builder.RegisterType<StoreService>().As<IStoreService>().InstancePerLifetimeScope();
            builder.RegisterType<StoreMappingService>().As<IStoreMappingService>().InstancePerLifetimeScope();
            builder.RegisterType<LanguageService>().As<ILanguageService>().InstancePerLifetimeScope();
            builder.RegisterType<DbLogger>().As<ILogger>().InstancePerLifetimeScope();
            builder.RegisterType<DateTimeHelper>().As<IDateTimeHelper>().InstancePerLifetimeScope();
            builder.RegisterType<RoutePublisher>().As<IRoutePublisher>().SingleInstance();
            builder.RegisterType<CacheKeyService>().As<ICacheKeyService>().InstancePerLifetimeScope();
            builder.RegisterType<SettingService>().As<ISettingService>().InstancePerLifetimeScope();
            builder.RegisterType<GenericAttributeService>().As<IGenericAttributeService>().InstancePerLifetimeScope();
            builder.RegisterType<AuthenticationProvider>().As<IAuthenticationProvider>().InstancePerLifetimeScope();
            builder.RegisterType<CookieAuthenticationService>()
                .Named<IAuthenticationService>(typeof(CookieAuthenticationService).Name)
                .As<IAuthenticationService>()
                .InstancePerLifetimeScope();
            builder.RegisterType<JwtAuthenticationService>()
                .Named<IAuthenticationService>(typeof(JwtAuthenticationService).Name)
                .As<IAuthenticationService>()
                .InstancePerLifetimeScope();

            builder.RegisterType<ExternalAuthenticationService>().As<IExternalAuthenticationService>().InstancePerLifetimeScope();
            builder.RegisterType<CustomerService>().As<ICustomerService>().InstancePerLifetimeScope();
            builder.RegisterType<CustomerRegistrationService>().As<ICustomerRegistrationService>().InstancePerLifetimeScope();
            builder.RegisterType<CaptchaService>().As<ICaptchaService>().InstancePerLifetimeScope();
            builder.RegisterType<SMSAccountService>().As<ISMSAccountService>().InstancePerLifetimeScope();
            builder.RegisterType<SMSTemplateService>().As<ISMSTemplateService>().InstancePerLifetimeScope();
            builder.RegisterType<AliyunSMSSender>().As<ISMSSender>().InstancePerLifetimeScope();
            builder.RegisterType<EmailAccountService>().As<IEmailAccountService>().InstancePerLifetimeScope();
            builder.RegisterType<EmailMessageTemplateService>().As<IEmailMessageTemplateService>().InstancePerLifetimeScope();
            builder.RegisterType<SmtpBuilder>().As<ISmtpBuilder>().InstancePerLifetimeScope();
            builder.RegisterType<AliyunEmailSender>().As<IEmailSender>().InstancePerLifetimeScope();
            builder.RegisterType<EmailMessageTemplateTokenProvider>().As<IEmailMessageTemplateTokenProvider>().InstancePerLifetimeScope();
            builder.RegisterType<QueuedEmailService>().As<IQueuedEmailService>().InstancePerLifetimeScope();
            builder.RegisterType<WorkflowMessageService>().As<IWorkflowMessageService>().InstancePerLifetimeScope();
            builder.RegisterType<SystemModuleService>().As<ISystemModuleService>().InstancePerLifetimeScope();
            builder.RegisterType<PermissionService>().As<IPermissionService>().InstancePerLifetimeScope();
            
            //DavidLee For ScheduleTask with Quartz.NET support
            builder.RegisterType<ScheduleTaskService>().As<IScheduleTaskService>().InstancePerLifetimeScope();
            builder.RegisterType<StdSchedulerFactory>().As<ISchedulerFactory>().InstancePerLifetimeScope();
            builder.RegisterType<IOCJobFactory>().As<IJobFactory>().InstancePerLifetimeScope();
            builder.RegisterType<TaskThread>().AsSelf().InstancePerLifetimeScope();
            //plugin managers
            builder.RegisterGeneric(typeof(PluginManager<>)).As(typeof(IPluginManager<>)).InstancePerLifetimeScope();
            builder.RegisterType<AuthenticationPluginManager>().As<IAuthenticationPluginManager>().InstancePerLifetimeScope();

            //register all settings
            builder.RegisterSource(new SettingsSource());
            //doaminEvent Dispatch services
            builder.RegisterType<EventDispatcher>().As<IEventDispatcher>().SingleInstance();
            //event handlers
            var handlers = typeFinder.FindClassesOfType(typeof(IEventHandler<>)).ToList();
            foreach (var hlr in handlers)
            {
                builder.RegisterType(hlr)
                    .As(hlr.FindInterfaces((type, criteria) =>
                    {
                        var isMatch = type.IsGenericType && ((Type)criteria).IsAssignableFrom(type.GetGenericTypeDefinition());
                        return isMatch;
                    }, typeof(IEventHandler<>)))
                    .InstancePerLifetimeScope();
            }
            //picture service
            builder.RegisterType<MimeTypesService>().As<IMimeTypesService>().SingleInstance();
            builder.RegisterType<MediaServiceProvider>().As<IMediaProvider>().InstancePerLifetimeScope();
            builder.RegisterType<MediaFileLocalService>()
                .Named<IMediaFileService>(typeof(MediaFileLocalService).Name)
                .As<IMediaFileService>()
                .InstancePerLifetimeScope();
            builder.RegisterType<MediaFileQingStorService>()
                .Named<IMediaFileService>(typeof(MediaFileQingStorService).Name)
                .As<IMediaFileService>()
                .InstancePerLifetimeScope();
            
            builder.RegisterType<AclService>().As<IAclService>().SingleInstance();
            builder.RegisterType<NaviMenuService>().As<INaviMenuService>().SingleInstance();

        }

        /// <summary>
        /// Gets order of this dependency registrar implementation
        /// </summary>
        public int Order => 0;
    }


    /// <summary>
    /// Setting source
    /// </summary>
    public class SettingsSource : IRegistrationSource
    {
        private static readonly MethodInfo _buildMethod =
            typeof(SettingsSource).GetMethod("BuildRegistration", BindingFlags.Static | BindingFlags.NonPublic);


        /// <summary>
        /// Registrations for
        /// </summary>
        /// <param name="service">Service</param>
        /// <param name="registrations">Registrations</param>
        /// <returns>Registrations</returns>
        public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, 
            Func<Service, IEnumerable<ServiceRegistration>> registrationAccessor)
        {
            var ts = service as TypedService;
            if (ts != null && typeof(ISettings).IsAssignableFrom(ts.ServiceType))
            {
                var buildMethod = _buildMethod.MakeGenericMethod(ts.ServiceType);
                yield return (IComponentRegistration)buildMethod.Invoke(null, null);
            }
        }
        
        private static IComponentRegistration BuildRegistration<TSettings>() where TSettings : ISettings, new()
        {
            return RegistrationBuilder
                .ForDelegate((c, p) =>
                {
                    Store store;

                    try
                    {
                        store = c.Resolve<IStoreContext>().CurrentStore;
                    }
                    catch
                    {
                        throw;
                    }

                    var currentStoreId = store?.Id ?? 0;

                    //uncomment the code below if you want load settings per store only when you have two stores installed.
                    //var currentStoreId = c.Resolve<IStoreService>().GetAllStores().Count > 1
                    //    c.Resolve<IStoreContext>().CurrentStore.Id : 0;

                    //although it's better to connect to your database and execute the following SQL:
                    //DELETE FROM [Setting] WHERE [StoreId] > 0
                    try
                    {
                        return c.Resolve<ISettingService>().LoadSetting<TSettings>(currentStoreId);
                    }
                    catch
                    {
                        throw;
                    }
                })
                .InstancePerLifetimeScope()
                .CreateRegistration();
        }

        /// <summary>
        /// Is adapter for individual components
        /// </summary>
        public bool IsAdapterForIndividualComponents => false;
    }

}
